package wsi.resolve.depr;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.rygh.core.html.Html;
import org.rygh.core.html.element.HtmlDiv;
import org.rygh.core.html.table.HtmlTable;
import org.rygh.core.html.table.TableData;
import org.rygh.core.html.table.TableTuple;
import org.rygh.core.model.common.MapList;
import org.rygh.core.model.common.MapSet;
import org.rygh.core.model.common.Pair;
import org.rygh.core.util.UIo;
import org.rygh.core.util.counter.FrequencyCounter;
import org.rygh.core.util.counter.SimpleCounter;
import org.rygh.core.xml.XmlPath;
import org.rygh.semantic.gui.swing.input.Input;
import org.rygh.semantic.model.ex.FactSet;
import org.rygh.semantic.model.rdf.Entity;
import org.rygh.semantic.model.rdf.Fact;
import org.rygh.semantic.model.rdf.Predicate;

import wsi.dataguide.tree.WSXmlLeaf;
import wsi.dataguide.tree.WSXmlTree;
import wsi.function.DirManager;
import wsi.gui.tool.DBInjector;
import wsi.match.MatchPair;
import wsi.match.MatchProperty;
import wsi.match.SubtreeMatch;
import wsi.match.SubtreeMatcher;
import wsi.subgraph.tree.EntityLeaf;
import wsi.subgraph.tree.EntityObject;
import wsi.subgraph.tree.EntityTree;

public class TripleGenerator extends SubtreeMatcher {

	static class MatchInputKey extends Pair<String, Input> {
		private final String key;

		public MatchInputKey(String k, Input input) {
			super(k, input);
			key = getKey();
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			MatchInputKey other = (MatchInputKey) obj;
			if (key == null) {
				if (other.key != null)
					return false;
			} else if (!key.equals(other.key))
				return false;
			return true;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((key == null) ? 0 : key.hashCode());
			return result;
		}

	}

	private final static double PERCENTAGE_VALID_SUBTREES = 0.85;
	private final static String ENT_INPUT = "?e_in";
	private final static String ENT_XML = "?e_xml";
	private static String ENT_TREE = "?e_tree";

	private final Map<Input, FactSet> rdfMap = new HashMap<>();
	protected final SimpleCounter<Fact> factSupCounter = new SimpleCounter<>();
	protected final SimpleCounter<Fact> factCounter = new SimpleCounter<>();
	protected final SimpleCounter<Fact> tempFactSupCounter = new SimpleCounter<>();
	protected final SimpleCounter<Fact> tempFactCounter = new SimpleCounter<>();
	protected final SimpleCounter<Integer> indexCounter = new SimpleCounter<>();
	protected final FrequencyCounter<String> subMatchSepCounter = new FrequencyCounter<>();
	protected final FrequencyCounter<String> supMatchSepCounter = new FrequencyCounter<>();

	// entity node to xpath in data guide.
	private final MapList<EntityLeaf, WSXmlLeaf> nodeMap = new MapList<>();

	private final MapSet<String, Input> hitInputMap = new MapSet<>();
	private final SimpleCounter<MatchInputKey> matchInputCounter = new SimpleCounter<>();

	// xml of subtree node to var
	private final Map<WSXmlLeaf, Input> varMap = new HashMap<>();

	/**
	 * var to xpath in dataguide
	 * 
	 * x matched nodes in subtree
	 * 
	 * y unmatched nodes in subtree
	 * 
	 * u matched nodes in sup-nodes
	 * 
	 * v unmatched nodes in sup-nodes
	 */
	protected final Map<Input, WSXmlLeaf> valueMap = new HashMap<>();

	private int varIndex = 1;

	private Boolean isExistsMatch = null;

	private final SimpleCounter<String> subMatchFrequency;
	private final SimpleCounter<String> supMatchFrequency;
	private final SimpleCounter<WSXmlLeaf> unmatchedCounter;
	private final SimpleCounter<WSXmlLeaf> unmatchedSupCounter;

	public TripleGenerator(DirManager dir) {
		super(dir);

		subMatchFrequency = subtreeCounter.getSubMatchFrequency();
		supMatchFrequency = subtreeCounter.getSupMatchFrequency();
		unmatchedCounter = subtreeCounter.getUnmatchedCounter();
		unmatchedSupCounter = subtreeCounter.getUnmatchedSupCounter();
	}

	private Fact addFact(Entity subj, Predicate pred, Entity obj,
			boolean inverse, boolean isSuper, boolean isTemp, FactSet group) {
		SimpleCounter<Fact> counter = isTemp ? (isSuper ? tempFactSupCounter
				: tempFactCounter) : (isSuper ? factSupCounter : factCounter);
		Fact fact;

		if (!inverse)
			fact = new Fact(subj, pred, obj);
		else
			fact = new Fact(obj, pred, subj);

		if (group != null) {
			if (!group.contains(fact)) {
				group.add(fact);
				counter.count(fact);
			}
		} else
			counter.count(fact);

		// if (pred.getBodyString().endsWith("label")) {
		// System.out.println("add fact " + fact);
		// System.out.println("fact counter " + factCounter.getMap());
		// }

		return fact;
	}

	private void checkRelationship() {
		if (isExistsMatch()) {
			int tree_count = 0;
			int match_count = 0;
			int parent_root_count = 0;

			EntityLeaf groot, node, parent;

			int best_index = indexCounter.getMaxObject();
			List<WSXmlLeaf> good = new ArrayList<>();

			for (Entry<Input, List<WSXmlLeaf>> e : subrootMapList.entrySet()) {

				good.clear();
				good.add(e.getValue().get(best_index - 1));

				tree_count += good.size();

				for (WSXmlLeaf r : good) {
					SubtreeMatch sm = matchMap.get(r);

					if (sm == null)
						continue;

					groot = sm.getGraphRoot();

					for (MatchPair mp : sm.getMatchPairs()) {
						node = mp.getEntityLeaf();

						if (node == groot)
							continue;

						match_count++;

						parent = node.getParent();

						if (parent.isRoot())
							parent_root_count++;
					}

				}
			}

			System.out.printf(
					"parent_root_count %d, match_count %d, tree_count %d\n",
					parent_root_count, match_count, tree_count);

			if (1.0 * parent_root_count / match_count > .6)
				ENT_TREE = ENT_INPUT;

		} else {

		}
	}

	/**
	 * if both entity tree and xml tree contain/do not contain node and xpath of
	 * this pair, then we count as a match.
	 */
	private void compensate(SimpleCounter<String> counter, boolean isSuper) {
		boolean isExistsMatch = isExistsMatch();

		for (String k : counter.keySet()) {
			log.info("COMPENSATE for " + k);
			/*
			 * many pairs may have the same key (xpath = predicate). here the
			 * node of this pair mp may belong to a certain entity tree.
			 */
			MatchPair mp = (MatchPair) counter.getValue(k);
			assert mp != null;

			Set<Input> hitinputs = hitInputMap.get(k);

			int new_hit = 0;

			if (isSuper) {

				supMatchSepCounter.set(k, counter.getValue(k), counter.get(k),
						counter.getNormalizeFactor());

				for (Entry<Input, WSXmlTree> e : subtreeController
						.getXmlTreeMap().entrySet()) {
					Input input = e.getKey();
					WSXmlLeaf r = e.getValue().getConcreteRoot();

					log.info("    test supnodeMap input " + input);

					if (hitinputs.contains(input)) {
						new_hit += matchInputCounter.get(new MatchInputKey(k,
								input));
						log.info("hit input");
						continue;
					}

					EntityTree entityTree = entityTreeMap.get(input);
					assert entityTree != null;

					if (entityTree.canMapToAnyNode(mp.getEntityLeaf())) {
						log.info("mapping found in entity tree for "
								+ mp.getEntityLeaf());
						continue;
					}

					//

					if (r.containsLeafEndsWith(mp.getXmlLeaf().getXpath())) {
						log.info("mapping found in xml tree for "
								+ mp.getXmlLeaf());
						continue;
					}

					new_hit++;
					log.info("    @ input " + input + ": " + new_hit);
				}

				supMatchSepCounter.set(k, counter.getValue(k), new_hit,
						counter.getNormalizeFactor());

			} else {

				subMatchSepCounter.set(k, counter.getValue(k), counter.get(k),
						counter.getNormalizeFactor());

				List<WSXmlLeaf> good = new ArrayList<>();

				for (Entry<Input, List<WSXmlLeaf>> e : subrootMapList
						.entrySet()) {
					Input input = e.getKey();

					log.info("    test subrootMap input " + input);

					if (hitinputs.contains(input)) {
						new_hit += matchInputCounter.get(new MatchInputKey(k,
								input));
						log.info("hit input");
						continue;
					}

					EntityTree entityTree = entityTreeMap.get(input);
					assert entityTree != null;

					if (entityTree.canMapToAnyNode(mp.getEntityLeaf())) {
						log.info("mapping found in entity tree for "
								+ mp.getEntityLeaf());
						continue;
					}

					//

					good.clear();

					if (isExistsMatch) {
						int best_index = indexCounter.getMaxObject();
						good.add(e.getValue().get(best_index - 1));

						assert good.get(0).getXpath().getLastIndex() == best_index : good
								.get(0).getXpath().getLastIndex();
					} else
						good.addAll(e.getValue());

					boolean mapped = false;

					for (WSXmlLeaf r : good)
						if (r.containsLeafEndsWith(mp.getXmlLeaf().getXpath())) {
							mapped = true;
							break;
						}

					if (mapped) {
						log.info("mapping found in xml tree for "
								+ mp.getXmlLeaf());
						continue;
					}

					new_hit++;
					log.info("    @ input " + input + ": " + new_hit);
				}

				subMatchSepCounter.set(k, counter.getValue(k), new_hit,
						counter.getNormalizeFactor());
			}

			assert new_hit <= counter.getNormalizeFactor() : String.format(
					"new_hit %d, norm %d", new_hit,
					counter.getNormalizeFactor());
			assert new_hit >= counter.get(k) : String.format(
					"new_hit %d, old_hit %d", new_hit, counter.get(k));

		}

	}

	@Override
	public void compute() {
		super.compute();

		if (isExistsMatch())
			computeExistsMatch();
		else
			computeAllMatch();
	}

	private void computeAllMatch() {
		log.info("Deteceted an all-match for function " + function.getName());

		int subtreeCount = 0;

		for (Entry<Input, List<WSXmlLeaf>> e : subrootMapList.entrySet()) {
			Input input = e.getKey();
			List<WSXmlLeaf> subroots = e.getValue();

			log.info("processing input " + input.getValue());

			subtreeCount += subroots.size();

			if (supnodeMap.containsKey(input))
				prepareVarValueMaps(supnodeMap.get(input));

			for (WSXmlLeaf subroot : subroots)
				if (matchMap.containsKey(subroot))
					prepareVarValueMaps(matchMap.get(subroot));

			if (supnodeMap.containsKey(input))
				generateFactTemplate(input, supnodeMap.get(input), true);

			for (WSXmlLeaf subroot : subroots)
				if (matchMap.containsKey(subroot))
					generateFactTemplate(input, matchMap.get(subroot), false);
		}

		// processing unmatched leaves.
		generateUnmatchedFactTemplate();
		generateUnmatchedFactTemplateSup();

		supMatchFrequency.setNormalizeFactor(supnodeMap.size());
		subMatchFrequency.setNormalizeFactor(subtreeCount);
		factSupCounter.setNormalizeFactor(supnodeMap.size());
		factCounter.setNormalizeFactor(subtreeCount);
		tempFactSupCounter.setNormalizeFactor(supnodeMap.size());
		tempFactCounter.setNormalizeFactor(subtreeCount);

		compensate(supMatchFrequency, true);
		compensate(subMatchFrequency, false);

		// instantiate triples

		for (Entry<Input, List<WSXmlLeaf>> e : subrootMapList.getMap()
				.entrySet()) {
			Input input = e.getKey();
			List<WSXmlLeaf> roots = e.getValue();

			for (WSXmlLeaf subroot : roots) {
				instantiateFactTemplate(input, subroot);
			}
		}
	}

	/**
	 * 13-07-14 <s>the first loop is to get the best index for exist match. for
	 * the second loop, best subtree will always be retrieved from the best
	 * index. This way, for some input where the "best" subtree disagrees with
	 * the best index, we can still locate the true best subtree.</s><br>
	 * 
	 * fixating the location applies only to instantiation, for other cases, try
	 * real best subtrees, to increase precision and recall.
	 */
	private void computeExistsMatch() {
		log.info("Deteceted an exists-match for function " + function.getName());

		int goodSubtree = 0;
		int subtreeCount = 0;
		List<WSXmlLeaf> good;

		for (Entry<Input, List<WSXmlLeaf>> e : subrootMapList.entrySet()) {
			Input input = e.getKey();
			good = getBestSubtrees(e.getValue());

			if (good.isEmpty()) {
				log.info(String
						.format("No good subtrees for %s, try to generate for supnodes.",
								input.getValue()));

				if (supnodeMap.containsKey(input))
					generateFactTemplate(input, supnodeMap.get(input), true);

				continue;
			}

			if (supnodeMap.containsKey(input))
				prepareVarValueMaps(supnodeMap.get(input));

			// subroots

			goodSubtree += good.size();
			subtreeCount += e.getValue().size();

			log.info(String.format("Obtained %d good subtrees for %s",
					good.size(), input.getValue()));

			for (WSXmlLeaf subroot : good) {
				if (matchMap.containsKey(subroot)) {
					prepareVarValueMaps(matchMap.get(subroot));
				}
			}

			if (supnodeMap.containsKey(input))
				generateFactTemplate(input, supnodeMap.get(input), true);

			for (WSXmlLeaf subroot : good) {
				log.info("generate for " + subroot.getXpath());

				if (matchMap.containsKey(subroot)) {
					generateFactTemplate(input, matchMap.get(subroot), false);
				}
			}
		}

		// we need to check relationship among e_in e_tree e_xml.

		checkRelationship();

		// processing unmatched leaves.
		generateUnmatchedFactTemplate();
		generateUnmatchedFactTemplateSup();

		// usually goodSubtree is equal to inputSize

		supMatchFrequency.setNormalizeFactor(supnodeMap.size());
		subMatchFrequency.setNormalizeFactor(goodSubtree);
		factSupCounter.setNormalizeFactor(supnodeMap.size());
		factCounter.setNormalizeFactor(goodSubtree);
		tempFactSupCounter.setNormalizeFactor(supnodeMap.size());
		tempFactCounter.setNormalizeFactor(subtreeCount);

		indexCounter.setNormalizeFactor(subrootMapList.size());

		// call after the normalized factor is set.
		compensate(supMatchFrequency, true);
		compensate(subMatchFrequency, false);

		/*
		 * instantiation triples: when instantiating, simulate the procedure
		 * where we pick a subtree at a fixed location.
		 */

		int best_index = indexCounter.getMaxObject();
		log.info("Best index is " + best_index);
		good = new ArrayList<>();

		for (Entry<Input, List<WSXmlLeaf>> e : subrootMapList.getMap()
				.entrySet()) {
			Input input = e.getKey();

			List<WSXmlLeaf> subroots = e.getValue();

			if (subroots.size() <= best_index)
				continue;

			good.clear();
			good.add(e.getValue().get(best_index - 1));

			assert good.get(0).getXpath().getLastIndex() == best_index : good
					.get(0).getXpath().getLastIndex();

			for (WSXmlLeaf subroot : good) {
				instantiateFactTemplate(input, subroot);
			}
		}
	}

	private void generateFactTemplate(Input input, SubtreeMatch sm,
			boolean isSuper) {
		EntityLeaf node, parent, groot = sm.getGraphRoot();

		WSXmlLeaf dg_leaf, leaf;
		FactSet group = new FactSet();

		/*
		 * we assume that one subtree contains a unique key consider
		 * .../alias-list/alias[1]/@sort-name = u:label and
		 * .../alias-list/alias[2]/@sort-name = u:label. both positions contain
		 * similar values. "Ann Margaret" and "Ann-Margaret"
		 */
		Set<String> keySet = new HashSet<>();

		for (MatchPair p : sm.getMatchPairs()) {
			// System.out.println("[generateFactTemplate] MatchPair " + p);

			if (!keySet.contains(p.getKey()))
				keySet.add(p.getKey());
			else
				continue;

			if (isSuper)
				supMatchFrequency.count(p.getKey(), p);
			else
				subMatchFrequency.count(p.getKey(), p);

			hitInputMap.put(p.getKey(), input);
			matchInputCounter.count(new MatchInputKey(p.getKey(), input));

			node = p.getEntityLeaf();

			if (node == groot) {
				assert node.isRoot();
				continue;
			}

			parent = node.getParent();
			leaf = p.getXmlLeaf();
			dg_leaf = dataGuide.getIsomorphicLeaf(leaf);
			assert dg_leaf != leaf;

			EntityObject e_obj = node.getValue();
			EntityObject e_parent = parent.getValue();

			Entity subj, obj;
			Predicate pred;

			pred = e_obj.getPredicate();
			obj = new Entity(varMap.get(dg_leaf).getValue(), e_obj.getValue()
					.isLiteral());

			// System.out.println("leaf    " + leaf + "\ndg_leaf " + dg_leaf);

			if (parent.isRoot()) {
				subj = new Entity(ENT_INPUT, false);
				addFact(subj, pred, obj, e_obj.isInverse(), isSuper, false,
						group);
			} else {
				List<WSXmlLeaf> subjs = nodeMap.get(parent);
				assert subjs != null : parent.toString() + "\n"
						+ e_parent.toString();

				for (WSXmlLeaf l : subjs) {
					// if (dg_leaf == l)
					// continue;

					String val = varMap.get(l).getValue();
					subj = new Entity(val, e_parent.getValue().isLiteral());

					addFact(subj, pred, obj, e_obj.isInverse(), isSuper, false,
							group);
				}
			}
		}

		// System.out.println("\n\n\t" + group);
		// System.out.println("factCounter " + factCounter.getMap());
		// System.out.println("factSupCounter " + factSupCounter.getMap());
	}

	private void generateUnmatchedFactTemplate() {
		WSXmlLeaf common = subtreeController.getCommonRoot();

		for (WSXmlLeaf l : unmatchedLeaves) {
			Input var = prepareVarValueMaps(l, null, "?y");
			XmlPath p = l.getXpath().getRelativePathsTo(common.getXpath());

			Predicate pred = new Predicate("t:" + p.toPredicateString());
			Entity obj = new Entity(var.getValue(), false);

			// System.out.println("unmatched leaf " + p.getValue());
			// System.out.printf("unmatchedCounter %d %s\n",
			// unmatchedCounter.get(l), l.getXpath());

			Fact fact = addFact(new Entity(ENT_TREE, false), pred, obj, false,
					false, true, null);
			tempFactCounter.set(fact, unmatchedCounter.get(l));
		}
	}

	private void generateUnmatchedFactTemplateSup() {
		for (WSXmlLeaf l : unmatchedSupLeaves) {
			Input var = prepareVarValueMaps(l, null, "?v");
			XmlPath p = l.getXpath();

			Predicate pred = new Predicate("t:" + p.toPredicateString());
			Entity obj = new Entity(var.getValue(), false);

			// System.out.println("unmatched leaf " + p.getValue());

			Fact fact = addFact(new Entity(ENT_XML, false), pred, obj, false,
					true, true, null);

			if (unmatchedSupCounter.contains(l))
				tempFactSupCounter.set(fact, unmatchedSupCounter.get(l));
		}
	}

	/**
	 * it's possible there several good subtrees. consider querying Sinatra and
	 * both Frank Sinatra and Frank Sinatra Jr. are good subtrees.
	 * 
	 * @param subroots
	 * @return
	 */
	private List<WSXmlLeaf> getBestSubtrees(List<WSXmlLeaf> subroots) {
		List<WSXmlLeaf> goodroots = new ArrayList<>();
		/*
		 * subroots are the sub roots w.r.t. an input value
		 */

		double prob = 0;
		WSXmlLeaf root = null;

		for (WSXmlLeaf r : subroots)
			if (matchMap.containsKey(r)
					&& matchMap.get(r).getProbPositiveValidMatch() > prob) {
				prob = matchMap.get(r).getProbPositiveValidMatch();
				root = r;
			}

		if (root != null) {
			assert root.getXpath().getLastIndex() > 0 : root.getXpath();

			indexCounter.count(root.getXpath().getLastIndex());
			goodroots.add(root);
		}

		return goodroots;
	}

	private List<WSXmlLeaf> getGoodSubtrees(List<WSXmlLeaf> subroots) {
		List<WSXmlLeaf> goodroots = new ArrayList<>();
		/*
		 * subroots are the sub roots w.r.t. an input value
		 */

		for (WSXmlLeaf r : subroots)
			if (matchMap.containsKey(r)
					&& matchMap.get(r).getProbPositiveValidMatch() >= MatchProperty.Threshold.PERFECT_MATCH) {
				goodroots.add(r);
				indexCounter.count(r.getXpath().getLastIndex());
			}

		return goodroots;
	}

	private String getValue(List<WSXmlLeaf> leaves, WSXmlLeaf leaf) {
		WSXmlLeaf obj = null;

		for (WSXmlLeaf l : leaves) {
			if (leaf.getXpath().subsumesWithoutIndex(l.getXpath())) {
				obj = l;
				break;
			}
		}

		if (obj == null) {
			// System.out.println("getValue not found.");
			return null;
		}

		assert obj != null : leaf.getXpath().getValue();

		String val = obj.getValue().getValue();

		return val;
	}

	private void instantiateFactTemplate(Input input, WSXmlLeaf subroot) {
		Set<Fact> facts = factCounter.keySet();
		instantiateFactTemplate(input, subroot, facts);

		facts = tempFactCounter.keySet();
		instantiateFactTemplate(input, subroot, facts);
	}

	private void instantiateFactTemplate(Input input, WSXmlLeaf subroot,
			Set<Fact> facts) {
		log.info(String.format("for %s @ %s", input, subroot.getXpath()));

		if (facts.isEmpty())
			return;

		FactSet fg = new FactSet();
		Fact fact;

		WSXmlLeaf dg_leaf;
		Input var;
		String value, e_input = "y:" + input.getValue();

		List<WSXmlLeaf> leaves = subtreeController.getLeaves(subroot);

		Entity subj, obj;

		for (Fact f : facts) {
			subj = null;
			obj = null;

			if (!f.getSubject().isVariable())
				subj = f.getSubject();
			else {
				var = new Input(f.getSubject(), null, f.getSubject()
						.getBodyString());

				if (var.getValue().equals(ENT_INPUT)) {
					subj = new Entity(e_input, f.getSubject().isLiteral());
				} else {
					dg_leaf = valueMap.get(var);

					if (dg_leaf == null) {
						log.info("Unresolved variable " + var);
						continue;
					}

					value = getValue(leaves, dg_leaf);

					if (value != null && !value.isEmpty())
						subj = new Entity("y:" + value);
					else
						continue;

					if (subj.isLiteral())
						continue;
				}
			}

			if (!f.getObject().isVariable())
				obj = f.getObject();
			else {
				var = new Input(f.getObject(), null, f.getObject()
						.getBodyString());

				if (var.getValue().equals(ENT_INPUT)) {
					obj = new Entity(e_input, f.getObject().isLiteral());
				} else {
					dg_leaf = valueMap.get(var);
					value = getValue(leaves, dg_leaf);

					if (value != null && !value.isEmpty())
						obj = new Entity(value, f.getObject().isLiteral());
					else
						continue;
				}
			}

			assert subj != null;
			assert obj != null;

			fact = new Fact(subj, f.getPredicate(), obj);

			fg.add(fact);
		}

		// System.out.println("instantiateFactTemplate " + fg);

		if (!rdfMap.containsKey(input))
			rdfMap.put(input, fg);
		else
			rdfMap.get(input).add(fg);
	}

	protected boolean isAllMatch() {
		return !isExistsMatch();
	}

	protected boolean isExistsMatch() {
		if (isExistsMatch != null)
			return isExistsMatch;

		log.info(String.format("subrootMap %d, matchMap %d.\n",
				subrootMapList.size(), matchMap.size()));

		if (subrootMapList.isEmpty() || matchMap.isEmpty()) {
			isExistsMatch = false;
			return isExistsMatch;
		}

		int hasOneCount = 0;

		for (List<WSXmlLeaf> subroots : subrootMapList.getMap().values())
			if (!getGoodSubtrees(subroots).isEmpty())
				hasOneCount++;

		log.info(String.format("Exists-match detection %d / %d", hasOneCount,
				subrootMapList.size()));

		isExistsMatch = 1. * hasOneCount / subrootMapList.size() >= PERCENTAGE_VALID_SUBTREES;

		return isExistsMatch;
	}

	private void organizeMatchInfo(MapList<String, String> matchMap,
			SimpleCounter<String> counter) {
		Predicate pred;
		String xpath;
		EntityObject eobj;

		for (String k : counter.keySet()) {
			MatchPair mp = (MatchPair) counter.getValue(k);

			xpath = mp.getXmlLeaf().getXpath().stripIndex();
			eobj = mp.getEntityLeaf().getValue();
			pred = eobj.getPredicate();

			// this means the mp.getNote() is root, should be pruned.
			if (pred == null)
				continue;

			if (eobj.isInverse())
				pred = new Predicate(pred.getValue() + "-");

			if (matchMap.containsKey(xpath))
				matchMap.get(xpath).add(pred.getValue());
			else {
				List<String> list = new ArrayList<>();
				list.add(pred.getValue());

				matchMap.put(xpath, list);
			}
		}
	}

	private void organizeMatchInfo(MapList<String, String> matchMap,
			SimpleCounter<WSXmlLeaf> counter, boolean isSuper) {
		WSXmlLeaf common = subtreeController.getCommonRoot();
		String xpath;
		String pred;
		XmlPath dgpath = new XmlPath("/dataguide");
		XmlPath new_path;

		for (WSXmlLeaf l : counter.keySet()) {
			new_path = l.getXpath().getRelativePathsTo(dgpath);
			new_path = new XmlPath("/" + new_path.getValue());

			xpath = new_path.stripIndex();

			if (isSuper)
				pred = l.getXpath().toPredicateString();
			else
				pred = l.getXpath().getRelativePathsTo(common.getXpath())
						.toPredicateString();

			pred = "t:" + pred;

			if (matchMap.containsKey(xpath))
				matchMap.get(xpath).add(pred);
			else {
				List<String> list = new ArrayList<>();
				list.add(pred);

				matchMap.put(xpath, list);
			}

		}
	}

	private void prepareVarValueMaps(SubtreeMatch sm) {
		WSXmlLeaf dg_leaf, leaf;
		EntityLeaf node;

		// subroot==null means it is a sup-node;
		String var = sm.getSubroot() == null ? "?u" : "?x";

		for (MatchPair p : sm.getMatchPairs()) {
			node = p.getEntityLeaf();
			leaf = p.getXmlLeaf();

			if (node.isRoot())
				continue;

			dg_leaf = dataGuide.getIsomorphicLeaf(leaf);
			prepareVarValueMaps(dg_leaf, node, var);
		}

		// System.out.println("varMap " + varMap);
		// System.out.println("nodeMap " + nodeMap);
	}

	private Input prepareVarValueMaps(WSXmlLeaf dg_leaf, EntityLeaf node,
			String prefix) {
		Input var;

		if (node != null)
			nodeMap.put(node, dg_leaf);

		if (varMap.containsKey(dg_leaf))
			var = varMap.get(dg_leaf);
		else {
			// System.out.println("new dg_leaf " + dg_leaf + " varIndex "
			// + varIndex);

			var = new Input(prefix + varIndex);
			varMap.put(dg_leaf, var);
			valueMap.put(var, dg_leaf);
			varIndex++;
		}

		return var;
	}

	@Override
	public String save() {
		File f = new File(dir.getDebugDir(), "_debug_triples_generated.html");
		f.delete();

		UIo.saveFile(
				new File(dir.getDebugDir(), "_debug_match_statistics.html"),
				toString());

		DBInjector.save(function, toJsonString());

		UIo.saveFile(
				new File(dir.getDebugDir(), "_debug_match_information.txt"),
				toMatchInfoString());

		return "";
	}

	private String toJsonString() {
		FactSet facts = new FactSet();

		for (FactSet g : rdfMap.values())
			facts.add(g);

		return facts.toJsonString();
	}

	private String toMatchInfoString() {
		StringBuilder sb = new StringBuilder();

		MapList<String, String> matchMap = new MapList<>();

		organizeMatchInfo(matchMap, subMatchFrequency);
		organizeMatchInfo(matchMap, supMatchFrequency);
		organizeMatchInfo(matchMap, unmatchedCounter, false);
		organizeMatchInfo(matchMap, unmatchedSupCounter, true);

		for (Entry<String, List<String>> e : matchMap.entrySet()) {
			sb.append(e.getKey());
			sb.append("\r\n");

			for (String s : e.getValue()) {
				sb.append("  ");
				sb.append(s);
				sb.append("\r\n");
			}
		}

		return sb.toString();
	}

	@Override
	public String toString() {
		Html html = new Html(dir.getServiceName() + "/" + function.getName());

		HtmlTable table;
		HtmlDiv div;
		TableTuple tuple;
		TableData data;

		subMatchFrequency.setName("Match Info for Subtrees");
		div = new HtmlDiv(subMatchFrequency.toHtmlTable());
		html.addElement(div);
		html.newline();

		subMatchSepCounter.setName("Match Info for Subtrees (Wilson)");
		div = new HtmlDiv(subMatchSepCounter.toHtmlTable());
		html.addElement(div);
		html.newline();

		supMatchFrequency.setName("Match Info for Sup-nodes");
		div = new HtmlDiv(supMatchFrequency.toHtmlTable());
		html.addElement(div);
		html.newline();

		supMatchSepCounter.setName("Match Info for Sup-nodes (Wilson)");
		div = new HtmlDiv(supMatchSepCounter.toHtmlTable());
		html.addElement(div);
		html.newline();

		factCounter.setName("Subtree Facts " + factCounter.size());
		div = new HtmlDiv(factCounter.toHtmlTable());
		html.addElement(div);
		html.newline();

		factSupCounter.setName("Sup-node Facts " + factSupCounter.size());
		div = new HtmlDiv(factSupCounter.toHtmlTable());
		html.addElement(div);
		html.newline();

		tempFactCounter.setName("Unmatched Subtree Facts "
				+ tempFactCounter.size());
		div = new HtmlDiv(tempFactCounter.toHtmlTable());
		html.addElement(div);
		html.newline();

		tempFactSupCounter.setName("Unmatched Sup-node Facts "
				+ tempFactSupCounter.size());
		div = new HtmlDiv(tempFactSupCounter.toHtmlTable());
		html.addElement(div);
		html.newline();

		indexCounter.setName("Best subtree counter " + indexCounter.size());
		div = new HtmlDiv(indexCounter.toHtmlTable());
		html.addElement(div);
		html.newline();

		data = new TableData();
		tuple = new TableTuple();
		tuple.add("Var", "XPath");
		data.add(tuple);

		for (Entry<Input, WSXmlLeaf> e : valueMap.entrySet()) {
			tuple = new TableTuple();
			tuple.add(e.getKey().getValue(), e.getValue().getXpath().getValue());
			data.add(tuple);
		}

		data.sort(0);

		table = new HtmlTable("Variables", data, false);
		html.addElement(table);
		html.newline();

		data = new TableData();

		tuple = new TableTuple();
		tuple.add("Input", "Triples");
		data.add(tuple);

		StringBuilder sb = new StringBuilder();

		for (Entry<Input, FactSet> e : rdfMap.entrySet()) {
			tuple = new TableTuple();

			tuple.add(e.getKey().getValue());

			sb.delete(0, sb.length());

			for (Fact f : e.getValue()) {
				sb.append(f.toString());
				sb.append("<br>");
			}

			tuple.add(sb.toString());
			data.add(tuple);
		}

		table = new HtmlTable("Triples", data, false);
		html.addElement(table);
		return html.toString();
	}

}
